/**
 * 3D Tiles工具类
 * 提供3D Tiles数据的加载、管理、样式设置等功能
 */
import * as Cesium from 'cesium'

class ThreeDTilesUtils {
  /**
   * 构造函数
   * @param {Object} viewer - Cesium Viewer实例
   */
  constructor(viewer) {
    this.viewer = viewer
    this.loadedTilesets = new Map() // 存储已加载的3D Tiles数据集信息
    this.tilesetIdCounter = 0 // 用于生成唯一的数据集ID
  }

  /**
   * 加载3D Tiles数据集
   * @param {string} url - 3D Tiles数据集的URL路径（通常指向tileset.json）
   * @param {Object} options - 加载选项
   * @param {number} options.maximumScreenSpaceError - 最大屏幕空间误差（默认2）
   * @param {boolean} options.dynamicScreenSpaceError - 是否启用动态屏幕空间误差（默认true）
   * @param {number} options.dynamicScreenSpaceErrorDensity - 动态屏幕空间误差密度（默认0.00278）
   * @param {boolean} options.flyTo - 是否自动定位到数据集（默认true）
   * @param {boolean} options.clampToGround - 是否将模型贴地（默认false）
   * @param {number} options.heightOffset - 模型高度偏移量（米，默认0）
   * @returns {Promise<string>} 加载的数据集ID
   */
  async loadTileset(url, options = {}) {
    if (!this.viewer) {
      throw new Error('Viewer实例未提供')
    }

    try {
      // 设置默认参数
      const defaultOptions = {
        maximumScreenSpaceError: 2,
        dynamicScreenSpaceError: true,
        dynamicScreenSpaceErrorDensity: 0.00278,
        flyTo: true,
        clampToGround: false,
        heightOffset: 0
      }

      const mergedOptions = { ...defaultOptions, ...options }
      const tilesetOptions = {
        url: url,
        maximumScreenSpaceError: mergedOptions.maximumScreenSpaceError,
        dynamicScreenSpaceError: mergedOptions.dynamicScreenSpaceError,
        dynamicScreenSpaceErrorDensity: mergedOptions.dynamicScreenSpaceErrorDensity
      }

      // 创建并加载3D Tiles数据集
      const tileset = await Cesium.Cesium3DTileset.fromUrl(url, tilesetOptions)
      this.viewer.scene.primitives.add(tileset)

      // 在 tileset.ready 后应用贴地或高度偏移，避免在模型未初始化时使用不正确的中心点
      const applyAfterReady = async () => {
        try {
          // 等待 readyPromise（如果存在）
          if (tileset && tileset.readyPromise) {
            await tileset.readyPromise
          }

          // 如果要求贴地，调用贴地逻辑（内部会再等待 readyPromise）
          if (mergedOptions.clampToGround) {
            this.setTilesetClampToGround(tileset, true, mergedOptions.heightOffset)
          } else if (mergedOptions.heightOffset !== 0) {
            // 使用模型包围球中心的地心法线方向进行偏移，避免简单 Z 轴平移的不准确
            const center = tileset.boundingSphere && tileset.boundingSphere.center
            if (center) {
              const carto = Cesium.Cartographic.fromCartesian(center)
              const normal = Cesium.Ellipsoid.WGS84.geodeticSurfaceNormalCartographic(carto)
              const translationVec = Cesium.Cartesian3.multiplyByScalar(normal, mergedOptions.heightOffset, new Cesium.Cartesian3())
              const translation = Cesium.Matrix4.fromTranslation(translationVec)
              const current = tileset.modelMatrix || Cesium.Matrix4.IDENTITY
              tileset.modelMatrix = Cesium.Matrix4.multiply(current, translation, new Cesium.Matrix4())
            } else {
              // 回退：直接沿 Z 轴平移
              tileset.modelMatrix = Cesium.Matrix4.fromTranslation(new Cesium.Cartesian3(0, 0, mergedOptions.heightOffset))
            }
          }
        } catch (e) {
          console.warn('tileset ready 后应用偏移/贴地失败:', e)
        }
      }
      applyAfterReady()

      // 生成唯一ID
      const tilesetId = `tileset_${this.tilesetIdCounter++}`

      // 存储数据集信息
      this.loadedTilesets.set(tilesetId, {
        id: tilesetId,
        url: url,
        tileset: tileset,
        name: options.name || `3D Tiles ${tilesetId}`,
        clampToGround: mergedOptions.clampToGround,
        heightOffset: mergedOptions.heightOffset
      })

      console.log(`成功加载3D Tiles数据集: ${options.name || url}`)

      // 如果设置了自动定位
      if (mergedOptions.flyTo) {
        this.flyToTileset(tilesetId)
      }

      return tilesetId
    } catch (error) {
      console.error('加载3D Tiles数据集失败:', error)
      throw error
    }
  }

  /**
   * 重新应用所有已标记为贴地的 tileset（在地形加载完成后使用）
   */
  reapplyAllClamps() {
    if (!this.viewer) return
    // 如果当前是椭球高程则无需处理
    if (this.viewer.terrainProvider instanceof Cesium.EllipsoidTerrainProvider) return

    this.loadedTilesets.forEach((info) => {
      try {
        if (info && info.clampToGround) {
          // 调用贴地逻辑，方法内部会检查 readyPromise
          this.setTilesetClampToGround(info.tileset, true, info.heightOffset || 0)
        }
      } catch (e) {
        console.warn('reapplyAllClamps 处理 tileset 失败:', e)
      }
    })
  }

  /**
   * 根据ID获取3D Tiles数据集
   * @param {string} tilesetId - 数据集ID
   * @returns {Object|null} 3D Tiles数据集对象，如果不存在则返回null
   */
  getTileset(tilesetId) {
    const tilesetInfo = this.loadedTilesets.get(tilesetId)
    return tilesetInfo ? tilesetInfo.tileset : null
  }

  /**
   * 设置3D Tiles数据集的样式
   * @param {string} tilesetId - 数据集ID
   * @param {Object|string} style - 样式配置对象或样式表达式
   * @returns {boolean} 操作是否成功
   */
  setTilesetStyle(tilesetId, style) {
    const tileset = this.getTileset(tilesetId)
    if (!tileset) {
      console.error(`未找到ID为${tilesetId}的3D Tiles数据集`)
      return false
    }

    try {
      if (typeof style === 'object') {
        tileset.style = new Cesium.Cesium3DTileStyle(style)
      } else if (typeof style === 'string') {
        tileset.style = new Cesium.Cesium3DTileStyle({ color: style })
      }
      console.log(`成功设置3D Tiles数据集样式: ${tilesetId}`)
      return true
    } catch (error) {
      console.error('设置3D Tiles数据集样式失败:', error)
      return false
    }
  }

  /**
   * 设置3D Tiles数据集的贴地属性
   * @param {string|Object} tileset - 数据集ID或数据集对象
   * @param {boolean} clampToGround - 是否贴地
   * @param {number} heightOffset - 高度偏移量（米）
   * @returns {boolean} 操作是否成功
   */
  setTilesetClampToGround(tileset, clampToGround, heightOffset = 0) {
    if (!this.viewer) {
      console.error('Viewer实例未提供')
      return false
    }

    // 获取 tileset 对象（支持传入 id 或对象）
    const tilesetObj = typeof tileset === 'string' ? this.getTileset(tileset) : tileset
    if (!tilesetObj) {
      console.error(`未找到3D Tiles数据集`)
      return false
    }

    try {
      // 移除之前可能注册的 readyPromise 回调标志
      if (tilesetObj._clampToGroundPending) {
        tilesetObj._clampToGroundPending = false
      }

      // 异步处理：等 tileset ready，然后基于地形采样计算一个整体偏移
      const applyClamp = async () => {
        try {
          if (tilesetObj.readyPromise) {
            await tilesetObj.readyPromise
          }

          // 计算 tileset 的包围球中心对应的经纬度
          const centerCartesian = tilesetObj.boundingSphere && tilesetObj.boundingSphere.center
          if (!centerCartesian) {
            console.warn('无法获取 tileset 包围球中心，跳过贴地')
            return
          }
          const centerCartographic = Cesium.Cartographic.fromCartesian(centerCartesian)

          // 如果没有地形或使用椭球高程，跳过采样
          let terrainHeight = null
          try {
            if (this.viewer.terrainProvider && !(this.viewer.terrainProvider instanceof Cesium.EllipsoidTerrainProvider)) {
              const samples = await Cesium.sampleTerrainMostDetailed(this.viewer.terrainProvider, [centerCartographic])
              terrainHeight = (samples && samples[0] && typeof samples[0].height === 'number') ? samples[0].height : null
            }
          } catch (e) {
            console.warn('采样地形高度失败，跳过贴地高度计算', e)
            terrainHeight = null
          }

          if (terrainHeight === null) {
            // 没有可用地形高度，使用简单偏移（或保持原样）
            if (!clampToGround) {
              tilesetObj.modelMatrix = Cesium.Matrix4.fromTranslation(new Cesium.Cartesian3(0, 0, heightOffset))
            }
            tilesetObj.clampToGround = Boolean(clampToGround)
            return
          }

          // 计算模型中心当前的高程（carto.height），然后计算差值
          const modelHeight = centerCartographic.height || 0
          const heightDifference = terrainHeight - modelHeight + (heightOffset || 0)

          // 构造平移矩阵并叠加到现有 modelMatrix
          // 使用地心法线（单位法向量）作为上方向，避免简单的 Z 轴平移在地球表面不准确的问题
          const normal = Cesium.Ellipsoid.WGS84.geodeticSurfaceNormalCartographic(centerCartographic)
          const translationVec = Cesium.Cartesian3.multiplyByScalar(normal, heightDifference, new Cesium.Cartesian3())
          const translation = Cesium.Matrix4.fromTranslation(translationVec)
          const currentMatrix = tilesetObj.modelMatrix || Cesium.Matrix4.IDENTITY
          const newMatrix = Cesium.Matrix4.multiply(currentMatrix, translation, new Cesium.Matrix4())
          tilesetObj.modelMatrix = newMatrix
          tilesetObj.clampToGround = Boolean(clampToGround)
          tilesetObj._heightOffset = heightOffset
        } catch (e) {
          console.error('applyClamp 异步执行失败:', e)
        }
      }

      // 更新 loadedTilesets 中的记录（如果存在对应条目）
      for (const [id, info] of this.loadedTilesets.entries()) {
        if (info && (info.tileset === tilesetObj || id === tileset)) {
          info.clampToGround = Boolean(clampToGround)
          info.heightOffset = heightOffset
          break
        }
      }

      // 标记并执行
      tilesetObj._clampToGroundPending = true
      applyClamp()

      console.log(`已触发3D Tiles贴地设置（异步）： ${clampToGround ? '贴地' : '不贴地'}, 高度偏移: ${heightOffset}米`)
      return true
    } catch (error) {
      console.error('设置3D Tiles数据集贴地属性失败:', error)
      return false
    }
  }

  /**
   * 定位到指定的3D Tiles数据集
   * @param {string} tilesetId - 数据集ID
   * @param {Object} options - 定位选项
   * @param {number} options.duration - 飞行持续时间（秒，默认3）
   * @param {number} options.offset - 相机偏移距离（默认视模型大小自动调整）
   * @returns {boolean} 操作是否成功
   */
  flyToTileset(tilesetId, options = {}) {
    const tileset = this.getTileset(tilesetId)
    if (!tileset || !this.viewer) {
      return false
    }

    try {
      // 设置默认飞行参数
      const defaultOptions = {
        duration: 3
      }
      
      const mergedOptions = { ...defaultOptions, ...options }
      
      // 飞行到数据集
      this.viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(
        0, 
        Cesium.Math.toRadians(-30), 
        options.offset || Math.max(tileset.boundingSphere.radius * 2, 500)
      ))
      
      console.log(`已定位到3D Tiles数据集: ${tilesetId}`)
      return true
    } catch (error) {
      console.error('定位到3D Tiles数据集失败:', error)
      return false
    }
  }

  /**
   * 显示/隐藏3D Tiles数据集
   * @param {string} tilesetId - 数据集ID
   * @param {boolean} show - 是否显示
   * @returns {boolean} 操作是否成功
   */
  showTileset(tilesetId, show) {
    const tileset = this.getTileset(tilesetId)
    if (!tileset) {
      return false
    }

    try {
      tileset.show = show
      console.log(`${show ? '显示' : '隐藏'}3D Tiles数据集: ${tilesetId}`)
      return true
    } catch (error) {
      console.error(`${show ? '显示' : '隐藏'}3D Tiles数据集失败:`, error)
      return false
    }
  }

  /**
   * 移除3D Tiles数据集
   * @param {string} tilesetId - 数据集ID
   * @returns {boolean} 操作是否成功
   */
  removeTileset(tilesetId) {
    if (!this.viewer || !this.loadedTilesets.has(tilesetId)) {
      return false
    }

    try {
      const tilesetInfo = this.loadedTilesets.get(tilesetId)
      this.viewer.scene.primitives.remove(tilesetInfo.tileset)
      this.loadedTilesets.delete(tilesetId)
      
      console.log(`成功移除3D Tiles数据集: ${tilesetInfo.name}`)
      return true
    } catch (error) {
      console.error('移除3D Tiles数据集失败:', error)
      return false
    }
  }

  /**
   * 获取所有已加载的3D Tiles数据集信息
   * @returns {Array} 数据集信息数组
   */
  getLoadedTilesets() {
    return Array.from(this.loadedTilesets.values()).map(tileset => ({
      id: tileset.id,
      name: tileset.name,
      url: tileset.url
    }))
  }

  /**
   * 创建基于高度的颜色样式
   * @param {Array} conditions - 颜色条件数组，格式：[[height, color], ...]
   * @param {string} defaultColor - 默认颜色（当不满足任何条件时使用）
   * @returns {Object} Cesium3DTileStyle配置对象
   */
  createHeightBasedStyle(conditions = [], defaultColor = 'color(\'blue\')') {
    // 确保条件按高度降序排序
    const sortedConditions = [...conditions].sort((a, b) => b[0] - a[0])
    
    // 构造安全的表达式：优先使用属性名 "Height"，其次尝试小写 "height"；
    // 使用 exists() 检查属性是否存在，避免 undefined 导致比较失败。
    const styleConditions = sortedConditions.map(condition => {
      const val = condition[0]
      const expr = `(exists(\${'{Height}'}) && \${'{Height}'} >= ${val}) || (exists(\${'{height}'}) && \${'{height}'} >= ${val})`
      return [expr, `color('${condition[1]}')`]
    })

    // 默认条件：所有都不满足时使用默认颜色
    styleConditions.push(['true', defaultColor])

    return {
      color: {
        conditions: styleConditions
      }
    }
  }

  /**
   * 设置3D Tiles数据集的渲染质量
   * @param {string} tilesetId - 数据集ID
   * @param {number} maximumScreenSpaceError - 最大屏幕空间误差（值越小越精细）
   * @returns {boolean} 操作是否成功
   */
  setRenderQuality(tilesetId, maximumScreenSpaceError) {
    const tileset = this.getTileset(tilesetId)
    if (!tileset) {
      return false
    }

    try {
      tileset.maximumScreenSpaceError = maximumScreenSpaceError
      console.log(`已设置3D Tiles数据集渲染质量: ${tilesetId}, 最大屏幕空间误差: ${maximumScreenSpaceError}`)
      return true
    } catch (error) {
      console.error('设置3D Tiles数据集渲染质量失败:', error)
      return false
    }
  }

  /**
   * 启用/禁用动态屏幕空间误差
   * @param {string} tilesetId - 数据集ID
   * @param {boolean} enabled - 是否启用
   * @param {number} density - 动态屏幕空间误差密度（仅在启用时有效）
   * @returns {boolean} 操作是否成功
   */
  setDynamicScreenSpaceError(tilesetId, enabled, density) {
    const tileset = this.getTileset(tilesetId)
    if (!tileset) {
      return false
    }

    try {
      tileset.dynamicScreenSpaceError = enabled
      if (enabled && typeof density === 'number') {
        tileset.dynamicScreenSpaceErrorDensity = density
      }
      console.log(`${enabled ? '启用' : '禁用'}3D Tiles数据集动态屏幕空间误差: ${tilesetId}`)
      return true
    } catch (error) {
      console.error(`${enabled ? '启用' : '禁用'}3D Tiles数据集动态屏幕空间误差失败:`, error)
      return false
    }
  }

  /**
   * 清理资源
   */
  destroy() {
    // 移除所有加载的3D Tiles数据集
    this.loadedTilesets.forEach((tilesetInfo, tilesetId) => {
      this.removeTileset(tilesetId)
    })

    this.loadedTilesets.clear()
    this.viewer = null
  }
}

export default ThreeDTilesUtils